package cn.virens.common.boot.rabbit;

import cn.virens.common.boot.rabbit.annotations.RabbitDeclareBind;
import cn.virens.common.boot.rabbit.annotations.RabbitDeclareQueue;
import cn.virens.common.boot.rabbit.autoconfig.*;
import cn.virens.common.util.exception.APIException;
import org.dromara.hutool.core.classloader.ClassLoaderUtil;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Binding.DestinationType;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.Argument;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.*;
import org.springframework.context.expression.StandardBeanExpressionResolver;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 基于RabbitAdmin实现根据注解自动注册队列
 *
 * @author virens
 */
public class RabbitManagerRegister implements BeanFactoryAware, BeanPostProcessor, InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(RabbitManagerRegister.class);

    private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
    private BeanExpressionContext context;
    private BeanFactory factory;

    private final RabbitManageProperties properties;
    private final RabbitAdmin admin;

    public RabbitManagerRegister(RabbitManageProperties properties, RabbitAdmin admin) {
        this.properties = properties;
        this.admin = admin;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        for (Method method : bean.getClass().getDeclaredMethods()) {
            this.declareQueue(bean, method);
            this.declareBind(bean, method);
        }

        return bean;
    }

    /**
     * 根据注解自动完成绑定关系初始化
     */
    protected void declareBind(Object bean, Method method) throws BeansException {
        RabbitDeclareBind bind = method.getAnnotation(RabbitDeclareBind.class);
        if (bind != null && StrUtil.isNotEmpty(bind.routingKey())) {
            var prop = new RabbitManagePropertiesBind();

            prop.setDestinationType(resolveString(bind.destinationType()));
            prop.setDestination(resolveString(bind.destination()));
            prop.setRoutingKey(resolveString(bind.routingKey()));
            prop.setExchange(resolveString(bind.exchange()));

            prop.setArguments(args(bind.arguments()));

            this.declareBind(prop);
        }
    }

    /**
     * 根据注解自动完成队列初始化
     */
    protected void declareQueue(Object bean, Method method) throws BeansException {
        RabbitDeclareQueue queue = method.getAnnotation(RabbitDeclareQueue.class);
        if (queue != null && StrUtil.isNotEmpty(queue.name())) {
            var prop = new RabbitManagePropertiesQueue();

            prop.setAutoDelete(resolveBoolean(queue.autoDelete()));
            prop.setExclusive(resolveBoolean(queue.exclusive()));
            prop.setDurable(resolveBoolean(queue.durable()));
            prop.setName(resolveString(queue.name()));

            prop.setArguments(args(queue.arguments()));

            this.declareQueue(prop);
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws APIException {
        if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
            this.context = new BeanExpressionContext(clbf, null);
            this.resolver = clbf.getBeanExpressionResolver();
        }

        this.factory = beanFactory;
    }

    protected Exchange newExchange(RabbitManagePropertiesExchange exchange) throws APIException {
        Class<Exchange> clazz = ClassLoaderUtil.loadClass(exchange.getImpl());
        if (clazz == null || StrUtil.isEmpty(exchange.getName())) {
            throw new APIException("交换机参数不正确");
        }

        return ConstructorUtil.newInstance(clazz,//
                exchange.getName(),//
                exchange.getDurable(),//
                exchange.getAutoDelete(),//
                args(exchange));
    }

    protected Queue newQueue(RabbitManagePropertiesQueue queue) throws APIException {
        return new Queue(queue.getName(),//
                queue.getDurable(),//
                queue.getExclusive(),//
                queue.getAutoDelete(),//
                args(queue));
    }

    protected Binding newBind(RabbitManagePropertiesBind bind) throws APIException {
        if (StrUtil.equals(bind.getDestinationType(), "exchange")) {
            return new Binding(bind.getDestination(),//
                    DestinationType.EXCHANGE,//
                    bind.getExchange(),//
                    bind.getRoutingKey(),//
                    args(bind));
        }

        return new Binding(bind.getDestination(),//
                DestinationType.QUEUE,//
                bind.getExchange(),//
                bind.getRoutingKey(),//
                args(bind));
    }

    protected Map<String, Object> args(RabbitManagePropertiesArgs declarable) throws APIException {
        if (declarable != null && declarable.getArguments() != null) {
            return declarable.getArguments();
        }

        return Map.of();
    }

    protected Map<String, Object> args(Argument[] arguments) throws APIException {
        Map<String, Object> answer = new HashMap<>();

        for (Argument argument : arguments) {
            Object value = switch (argument.type()) {
                case "java.lang.Boolean" -> ConvertUtil.toBoolean(argument.value());
                case "java.lang.Double" -> ConvertUtil.toDouble(argument.value());
                case "java.lang.Integer" -> ConvertUtil.toInt(argument.value());
                case "java.lang.Float" -> ConvertUtil.toFloat(argument.value());
                case "java.lang.String" -> ConvertUtil.toStr(argument.value());
                case "java.lang.Long" -> ConvertUtil.toLong(argument.value());
                default -> null;
            };

            if (argument.name() != null && value != null) {
                answer.put(argument.name(), value);
            }
        }

        return answer;
    }

    public void declareExchange(RabbitManagePropertiesExchange exchange) throws APIException {
        this.admin.declareExchange(newExchange(exchange));
        this.log.debug("Rabbit.交换机: " + exchange);
    }

    public void declareQueue(RabbitManagePropertiesQueue queue) throws APIException {
        this.admin.declareQueue(newQueue(queue));
        this.log.debug("Rabbit.队列: " + queue);
    }

    public void declareBind(RabbitManagePropertiesBind bind) throws APIException {
        this.admin.declareBinding(newBind(bind));
        this.log.debug("Rabbit.绑定: " + bind);
    }

    protected Boolean resolveBoolean(String inputValue) throws APIException {
        Object object = resolver.evaluate(resolve(inputValue), context);
        if (inputValue != null && object != null) {
            return ConvertUtil.toBoolean(object);
        }

        return ConvertUtil.toBoolean(inputValue);
    }

    protected String resolveString(String inputValue) throws APIException {
        Object object = resolver.evaluate(resolve(inputValue), context);
        if (inputValue != null && object != null) {
            return object.toString();
        }

        return inputValue;
    }

    protected String resolve(String value) throws APIException {
        if (this.factory instanceof ConfigurableBeanFactory cbf) {
            return cbf.resolveEmbeddedValue(value);
        }

        return value;
    }

    @Override
    public void afterPropertiesSet() throws APIException {
        properties.exchangeInit(this::declareExchange);
        properties.queueInit(this::declareQueue);
        properties.bindInit(this::declareBind);
    }
}
